﻿using Microsoft.BizTalk.Component;
using Microsoft.BizTalk.Component.Interop;
using Microsoft.BizTalk.Component.Utilities;
using Microsoft.BizTalk.Message.Interop;
using Microsoft.BizTalk.PipelineOM;
using Microsoft.BizTalk.Streaming;
using Microsoft.Practices.ESB.Exception.Management;
using Microsoft.Practices.ESB.Resolver;
using Microsoft.Practices.ESB.Itinerary;
using Microsoft.XLANGs.BaseTypes;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Resources;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml;

namespace BizTalk.ESB.Extensions.PipelineComponents
{
    [ComponentCategory(CategoryTypes.CATID_DisassemblingParser)]
    [ComponentCategory(CategoryTypes.CATID_PipelineComponent)]
    public class ItineraryListSelectorXmlDisassembler : XmlDasmComp, 
        IBaseComponent, IDisassemblerComponent, IPersistPropertyBag, IComponentUI, IProbeMessage
    {
        #region Properties

        //private static ResourceManager resManager = new ResourceManager("BizTalk.ESB.Extensions.PipelineComponents.ItineraryListSelectorXmlDisassembler",
        //    Assembly.GetExecutingAssembly());
        private const string DescriptionProperty = "Custom Pipeline Disassember for XML which attaches a list of itineraries to each message";
        private const string NameProperty = "Itinerary List Select XML Disassembler";
        private const string VersionProperty = "1.0";

        // Itinerary related properties
        private String _ItineraryFactValue;
        private String _ResolverConnectionStringValue;
        private Boolean _IgnoreError;
        private Boolean _ValidateItinerary;
        private Queue<IBaseMessage> _Messages = new Queue<IBaseMessage>();
        private List<IItinerary> _Itineraries = new List<IItinerary>();
        private String _LastMessage;

        // Keys for property management
        private const String IgnoreErrorKeyKey = "IgnoreErrorKey";
        private const String ItineraryFactKeyKey = "ItineraryFactKey";
        private const String ResolverConnStringKey = "ResolverConnectionString";
        private const String ValidateItineraryKey = "ValidateItinerary";
        private const String ItineraryInstanceKeyPrefix = "Resolver.Itinerary";
        
        #endregion

        public ItineraryListSelectorXmlDisassembler()
            : base()
        {
            base.AllowUnrecognizedMessage = false;
            base.DocumentSpecNames = new SchemaList();
            base.EnvelopeSpecNames = new SchemaList();
            base.RecoverableInterchangeProcessing = false;
            base.ValidateDocument = false;
        }

        #region Pipeline Configuration Properties

        [Description("Ignore error when the itinerary cannot be found in the configured repository.  Set to true to supress the error."), Browsable(true)]
        [DisplayName("Ignore Error")]
        public bool IgnoreErrorKey
        {
            get
            {
                return this._IgnoreError;
            }
            set
            {
                this._IgnoreError = value;
            }
        }

        [Description("Determine if Itinerary should be validated.  Generally this can be false if the itinerary is created through the designer."), Browsable(true)]
        [DisplayName("Validate Itinerary")]
        public bool ValidateItinerary
        {
            get
            {
                return this._ValidateItinerary;
            }
            set
            {
                this._ValidateItinerary = value;
            }
        }

        [Description("Contains the itinerary fact key string that defines itinerary fact provided by resolver."), Browsable(true)]
        [DisplayName("Itinerary Fact Key")]
        public String ItineraryFactKey
        {
            get
            {
                return this._ItineraryFactValue;
            }
            set
            {
                this._ItineraryFactValue = value;
            }
        }

        [Description("Contains connection string that defines itinerary arguments for resolver."), Browsable(true)]
        [DisplayName("Resolver Connection String")]
        public String ResolverConnectionString
        {
            get
            {
                return this._ResolverConnectionStringValue;
            }
            set
            {
                this._ResolverConnectionStringValue = value;
            }
        }

        #region XML Disassembler Properties

        // Override of base properties
        [BtsPropertyName("PropAllowUnrecognizedMessage"), BtsDescription("DescAllowUnrecognizedMessage")]
        new public bool AllowUnrecognizedMessage
        {
            get
            {
                return base.AllowUnrecognizedMessage;
            }
            set
            {
                base.AllowUnrecognizedMessage = value;
            }
        }

        [BtsPropertyName("PropDocumentSpecNames"), BtsDescription("DescDocumentSpecNames")]
        new public SchemaList DocumentSpecNames
        {
            get
            {
                return base.DocumentSpecNames;
            }
            set
            {
                base.DocumentSpecNames = value;
            }
        }

        [BtsDescription("DescEnvelopeSpecNames"), BtsPropertyName("PropEnvelopeSpecNames")]
        new public SchemaList EnvelopeSpecNames
        {
            get
            {
                return base.EnvelopeSpecNames;
            }
            set
            {
                base.EnvelopeSpecNames = value;
            }
        }

        [BtsPropertyName("PropRecoverableInterchangeProcessing"), BtsDescription("DescRecoverableInterchangeProcessing")]
        new public bool RecoverableInterchangeProcessing
        {
            get
            {
                return base.RecoverableInterchangeProcessing;
            }
            set
            {
                base.RecoverableInterchangeProcessing = value;
            }
        }

        [BtsPropertyName("PropValidate"), BtsDescription("DescValidate")]
        new public bool ValidateDocument
        {
            get
            {
                return base.ValidateDocument;
            }
            set
            {
                base.ValidateDocument = value;
            }
        }

        #endregion

        #endregion

        #region IBaseComponent

        new public string Description
        {
            get
            {
                //return resManager.GetString("ItineraryListSelectorXmlDisassemblerDesc");
                return DescriptionProperty;
            }
        }

        new public string Name
        {
            get
            {
                //return resManager.GetString("ItineraryListSelectorXmlDisassemblerName");
                return NameProperty;
            }
        }

        new public string Version
        {
            get
            {
                return "1.0";
            }
        }

        #endregion

        #region IPersistPropertyBag

        new public void GetClassID(out Guid classID)
        {
            classID = new Guid("A995D28D-9E5D-4660-82CD-528755C64E4D");
        }

        new public void InitNew()
        {
        }

        new public void Load(IPropertyBag propertyBag, int errorLog)
        {
            // Load Itinerary related properties
            Object o = ReadPropertyBag(propertyBag, ResolverConnStringKey);
            if (o != null)
            {
                this._ResolverConnectionStringValue = (String)o;
            }

            o = ReadPropertyBag(propertyBag, ItineraryFactKeyKey);
            if (o != null)
            {
                this._ItineraryFactValue = (String)o;
            }

            o = ReadPropertyBag(propertyBag, IgnoreErrorKeyKey);
            if (o != null)
            {
                this._IgnoreError = (Boolean)o;
            }

            o = ReadPropertyBag(propertyBag, ValidateItineraryKey);
            if (o != null)
            {
                this._ValidateItinerary = (Boolean)o;
            }

            base.Load(propertyBag, errorLog);
        }

        new public void Save(IPropertyBag propertyBag, bool clearDirty, bool saveAllProperties)
        {
            // Save Itinerary related properties
            WritePropertyBag(propertyBag, IgnoreErrorKeyKey, this._IgnoreError);
            WritePropertyBag(propertyBag, ResolverConnStringKey, this._ResolverConnectionStringValue);
            WritePropertyBag(propertyBag, ItineraryFactKeyKey, this._ItineraryFactValue);
            WritePropertyBag(propertyBag, ValidateItineraryKey, this._ValidateItinerary);

            base.Save(propertyBag, clearDirty, saveAllProperties);
        }

        #endregion

        #region IComponentUI

        new public IntPtr Icon
        {
            get
            {
                //return ((System.Drawing.Bitmap)resManager.GetObject("ItineraryListSelectorDisassemblerBitmap")).GetHicon();
                return new IntPtr();
            }
        }

        new public IEnumerator Validate(object projectSystem)
        {
            return base.Validate(projectSystem);
            //return null;
        }

        #endregion

        #region IProbeMessage

        new public bool Probe(IPipelineContext pContext, IBaseMessage pInMsg)
        {
            bool result = base.Probe(pContext, pInMsg);
            return result;
        }

        #endregion

        #region IDisassemblerComponent

        new public void Disassemble(IPipelineContext pContext, IBaseMessage pInMsg)
        {
            base.Disassemble(pContext, pInMsg);
        }

        /// <summary>
        /// This is called by the pipeline framework until it returns null
        /// </summary>
        /// <param name="pContext"></param>
        /// <returns></returns>
        new public IBaseMessage GetNext(IPipelineContext pContext)
        {
            // If No messages in the Queue, then add, 1 for each itinerary in the itinerary list
            if (this._Messages.Count > 0)
            {
                EventLogger.Write("Done with last itinerary for this message...getting next one...");
                return this.GetMessageFromQueue();
            }
            else
            {
                // Only call base.GetNext() when the queue is empty
                EventLogger.Write("Getting next message from Disassembler...");
                IBaseMessage nextMessage = base.GetNext(pContext);
                if (nextMessage != null)
                {
                    // Get list of itineraries for this message
                    EventLogger.Write("Retrieving Itinerary List...");
                    this.GetItineraryList(nextMessage, pContext);
                    EventLogger.Write("Itineraries in the list for this message: " + this._Itineraries.Count.ToString());

                    // Got another message from the disassembler...create copies for each itinerary in the list
                    if (this._Itineraries.Count > 0)
                    {
                        foreach (IItinerary currentItinerary in this._Itineraries)
                        {
                            EventLogger.Write("Adding Message to queue with itinerary...");
                            this._Messages.Enqueue(this.GetMessageForQueue(nextMessage, pContext, currentItinerary));
                        }
                    }
                    else
                    {
                        // No itineraries...just publish
                        EventLogger.Write("Adding message to queue WITHOUT itinerary...");
                        this._Messages.Enqueue(this.GetMessageForQueue(nextMessage, pContext));
                    }
                    this._LastMessage = String.Empty;
                    return this.GetMessageFromQueue();
                }
                else
                {
                    EventLogger.Write("All messages are done...");
                    return nextMessage;
                }
            }
        }

        #endregion

        #region Custom Members

        /// <summary>
        /// Populates the list of itineraries based on pipeline configuration and resolver info
        /// </summary>
        /// <param name="nextMessage"></param>
        /// <param name="pCtx"></param>
        private void GetItineraryList(IBaseMessage nextMessage, IPipelineContext pCtx)
        {
            this._Itineraries = new List<IItinerary>();
            EventLogger.Write("Resolver Connection String = " + this._ResolverConnectionStringValue);

            // Add Code to get Itinerary List
            Dictionary<String, String> dictionary = ResolverMgr.Resolve(
                ResolverMgr.GetResolverInfo(ResolutionType.Endpoint, this._ResolverConnectionStringValue)
                , nextMessage, pCtx);

            // Should now have our list of resolvers
            List<String> ItineraryTypes;

            // ItineraryFactKey = key in dictionary created by calling Resolve() which contains the itinerary comma-delimited list, "Resolver.ItineraryList"
            if ((dictionary != null) && dictionary.ContainsKey(this.ItineraryFactKey))  
            {
                // Get list of itineraries (ItineraryName.MajorVersion.MinorVersion) from the dictionary
                ItineraryTypes = new List<String>(dictionary[this.ItineraryFactKey].Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries));

                foreach (String itineraryType in ItineraryTypes)
                {
                    this._Itineraries.Add(this.RetrieveItinerary(dictionary[itineraryType]));
                }
            }
        }

        /// <summary>
        /// Gets the content of the message from the IBaseMessage stream
        /// </summary>
        /// <param name="pContext"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        private String GetMessageContent(IPipelineContext pContext, IBaseMessage message)
        {
            String msgData = string.Empty;
            Stream outStream = new VirtualStream();
            StreamWriter strmWriter = new StreamWriter(outStream);

            using (StreamReader sr = new
                          StreamReader(message.BodyPart.GetOriginalDataStream()))
            {
                msgData = sr.ReadToEnd();
                strmWriter.Flush(); // flush writer to ensure writing's done.
                strmWriter.Close(); // close stream since it won't be used
                sr.Close();
                return msgData;
            }
        }

        private IBaseMessage GetMessageFromQueue()
        {
            IBaseMessage nextMessageFromQueue = this._Messages.Dequeue();
            EventLogger.Write("Processing Message From Queue...");
            if (nextMessageFromQueue.BodyPart.Data != null)
            {
                EventLogger.Write("nextMessageFromQueue data stream is not null...");
                String streamType = nextMessageFromQueue.BodyPart.Data.GetType().ToString();
                EventLogger.Write("nextMessageFromQueue BodyPart.Data type = " + streamType);
                if (streamType.ToUpper().Contains("VIRTUALSTREAM"))
                {
                    VirtualStream v = (VirtualStream)nextMessageFromQueue.BodyPart.Data;
                    if (v.UnderlyingStream == null)
                    {
                        EventLogger.Write("ERROR ~~ nextMessageFromQueue BodyPart.Data underlying stream is null...");
                    }
                }
            }
            else
            {
                EventLogger.Write("ERROR ~~ Dequeued message data stream is null.");
            }

            Stream originalStream = nextMessageFromQueue.BodyPart.GetOriginalDataStream();
            if (originalStream != null)
            {
                EventLogger.Write("nextMessageFromQueue original data stream is not null...");
                String streamType = originalStream.GetType().ToString();
                EventLogger.Write("nextMessageFromQueue OriginalDataStream type = " + streamType);
                if (streamType.ToUpper().Contains("VIRTUALSTREAM"))
                {
                    VirtualStream v = (VirtualStream)originalStream;
                    if (v.UnderlyingStream == null)
                    {
                        EventLogger.Write("ERROR ~~ nextMessageFromQueue underlying stream is null...");
                    }
                    else
                    {
                        EventLogger.Write("nextMessageFromQueue underlying stream is NOT null...");
                        if (!nextMessageFromQueue.BodyPart.Data.Equals(originalStream))
                        {
                            EventLogger.Write("Message.BodyPart.Data != original data stream.");
                            if (originalStream.CanSeek)
                            {
                                originalStream.Position = 0L;
                                nextMessageFromQueue.BodyPart.Data = originalStream;
                            }
                            else
                            {
                                EventLogger.Write("ERROR ~~ Message original data stream is NOT SEEKABLE");
                            }
                        }
                    }
                }
            }
            else
            {
                System.Diagnostics.Trace.WriteLine("ERROR ~~ Message original data stream is null");
            }
            return nextMessageFromQueue;
        }

        private IBaseMessage GetMessageForQueue(IBaseMessage Msg, IPipelineContext pCtx)
        {
            IBaseMessage msg = Microsoft.Practices.ESB.Utilities.MessageHelper.CreateCloneMessage(pCtx, Msg);
            return msg;
        }

        private IBaseMessage GetMessageForQueue(IBaseMessage Msg, IPipelineContext pCtx, IItinerary Itinerary)
        {
            if (String.IsNullOrEmpty(this._LastMessage))
            {
                this._LastMessage = this.GetMessageContent(pCtx, Msg);
            }
            IBaseMessage msgCopy = this.CopyMessage(Msg, pCtx, this._LastMessage);

            EventLogger.Write("Message context count before writing itinerary: " + msgCopy.Context.CountProperties.ToString());
            if (this.ValidateItinerary)
            {
                Itinerary.Validate();
            }
            Itinerary.Initialize(msgCopy, Guid.NewGuid().ToString());
            Itinerary.Write(msgCopy);
            EventLogger.Write("Message context count after writing itinerary: " + msgCopy.Context.CountProperties.ToString());

            return msgCopy;
        }

        private IBaseMessage CopyMessage(IBaseMessage originalMessage, IPipelineContext pipelineContext, String messageContent)
        {
            Byte[] msgData = Encoding.ASCII.GetBytes(messageContent);
            Stream outStream = new MemoryStream(msgData);
            IBaseMessage msgCopy = this.CopyMessage(originalMessage, pipelineContext, outStream);
            return msgCopy;
        }

        private IBaseMessage CopyMessage(IBaseMessage originalMessage, IPipelineContext pipelineContext, Stream messageContent)
        {
            EventLogger.Write("Copying Message...");
            EventLogger.Write("Original Message Part Count: " + originalMessage.PartCount.ToString());

            // Message
            IBaseMessage msgCopy = pipelineContext.GetMessageFactory().CreateMessage();

            // Message Parts
            for (Int32 i = 0; i < originalMessage.PartCount; i++)
            {
                String partName = String.Empty;
                IBaseMessagePart part = originalMessage.GetPartByIndex(i, out partName);
                IBaseMessagePart partCopy = pipelineContext.GetMessageFactory().CreateMessagePart();
                EventLogger.Write("Copying message part " + partName);

                // Copy data stream
                messageContent.Seek(0, SeekOrigin.Begin);
                partCopy.Data = messageContent;
                pipelineContext.ResourceTracker.AddResource(partCopy.Data);

                // Copy properties
                partCopy.PartProperties = PipelineUtil.CopyPropertyBag(part.PartProperties, pipelineContext.GetMessageFactory());

                // Add part to message
                msgCopy.AddPart(partName, partCopy, (String.Compare(originalMessage.BodyPartName, partName, true, CultureInfo.CurrentCulture) == 0));
            }

            // Message Context
            msgCopy.Context = PipelineUtil.CloneMessageContext(originalMessage.Context);
            EventLogger.Write("Message Copy complete...");
            EventLogger.Write("Copied Message Part Count: " + msgCopy.PartCount.ToString());

            return msgCopy;
        }
        
        private IItinerary RetrieveItinerary(String ItineraryKey)
        {
            // This create method is expecting the Itinerary XML
            IItinerary i = ItineraryOMFactory.Create(ItineraryKey);
            return i;
        }

        /// <summary>
        /// Reads property value from property bag.
        /// </summary>
        /// <param name="pb">Property bag.</param>
        /// <param name="propName">Name of property.</param>
        /// <returns>Value of the property.</returns>
        private static object ReadPropertyBag(Microsoft.BizTalk.Component.Interop.IPropertyBag pb, string propName)
        {
            object val = null;
            try
            {
                pb.Read(propName, out val, 0);
            }

            catch (ArgumentException)
            {
                return val;
            }
            catch (Exception ex)
            {
                throw new ApplicationException(ex.Message);
            }
            return val;
        }

        /// <summary>
        /// Writes property values into a property bag.
        /// </summary>
        /// <param name="pb">Property bag.</param>
        /// <param name="propName">Name of property.</param>
        /// <param name="val">Value of property.</param>
        private static void WritePropertyBag(Microsoft.BizTalk.Component.Interop.IPropertyBag pb, string propName, object val)
        {
            try
            {
                pb.Write(propName, ref val);
            }
            catch (Exception ex)
            {
                throw new ApplicationException(ex.Message);
            }
        }

        #endregion
    }
}
